import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { AsPropWarning } from "../components/AsPropWarning";
import { Pipe } from "../components/Pipe";

<SEO
	title="Menu Button"
	description="An accessible component for the common dropdown menu button design pattern in React"
/>

# Menu Button

<TOC>
	<TOCList>
		<TOCLink href="#menu">Menu</TOCLink>
		<TOCLink href="#menubutton">MenuButton</TOCLink>
		<TOCLink href="#menulist">MenuList</TOCLink>
		<TOCLink href="#menupopover">MenuPopover</TOCLink>
		<TOCLink href="#menuitems">MenuItems</TOCLink>
		<TOCLink href="#menuitem">MenuItem</TOCLink>
		<TOCLink href="#menulink">MenuLink</TOCLink>
		<TOCLink href="#usemenubuttoncontext">useMenuButtonContext</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/menu-button
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#menubutton

An accessible dropdown menu for the common dropdown menu button design pattern.

Please note that the buttons on this page are styled by this website. They are just buttons, so they will appear the same as any other button in your app.

```jsx
// jsx-demo
function Example() {
	return (
		<Menu>
			<MenuButton>
				Actions <span aria-hidden>▾</span>
			</MenuButton>
			<MenuList>
				<MenuItem onSelect={() => alert("Download")}>Download</MenuItem>
				<MenuItem onSelect={() => alert("Copy")}>Create a Copy</MenuItem>
				<MenuItem onSelect={() => alert("Mark as Draft")}>
					Mark as Draft
				</MenuItem>
				<MenuItem onSelect={() => alert("Delete")}>Delete</MenuItem>
				<MenuLink as="a" href="https://reacttraining.com/workshops/">
					Attend a Workshop
				</MenuLink>
			</MenuList>
		</Menu>
	);
}
```

## Installation

From the command line in your project directory, run `npm install @reach/menu-button` or `yarn add @reach/menu-button`. Then import the components and styles that you need:

```bash
npm install @reach/menu-button
# or
yarn add @reach/menu-button
```

```js
import {
	Menu,
	MenuList,
	MenuButton,
	MenuItem,
	MenuItems,
	MenuPopover,
	MenuLink,
} from "@reach/menu-button";
import "@reach/menu-button/styles.css";
```

## Component API

### Menu

The wrapper component for the other components.

`Menu` does not render a DOM element by default, but you may pass the `as` prop if you want to wrap the child components. This may be useful if `portal` is set to `false` on either `MenuList` or `MenuItems` and you want to use a relatively positioned element to make styling the popover easier.

#### Menu CSS Selectors

```css
/* NOTE: This selector only works if a DOM node is rendered via the `as` prop  */
[data-reach-menu] {
}
```

#### Menu Props

| Prop                         | Type                          | Required |
| ---------------------------- | ----------------------------- | -------- |
| [`as`](#menu-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#menu-children) | `node`                        | false    |
| [`id`](#menu-id)             | `string`                      | false    |

##### Menu `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `Menu` what underlying element to render. Defaults to `React.Fragment`.

<AsPropWarning />

##### Menu `children`

`children: React.ReactNode | ((props: { isExpanded: boolean }) => React.ReactNode)`

Requires two children: a `MenuButton` and a `MenuList`.

```jsx
// jsx-demo
function Example() {
	return (
		<Menu>
			<MenuButton>Actions</MenuButton>
			<MenuList>
				<MenuItem>Download</MenuItem>
				<MenuLink to="view">View</MenuLink>
			</MenuList>
		</Menu>
	);
}
```

Alternatively, you can provide a render callback. This is helpful if you need to access the internal state of the Menu.

```jsx
// jsx-demo
function Example() {
	return (
		<Menu>
			{({ isExpanded }) => (
				<React.Fragment>
					<MenuButton>
						{isExpanded ? "Close" : "Open"} <span aria-hidden="true">▾</span>
					</MenuButton>
					<MenuList>
						<MenuItem>Download</MenuItem>
						<MenuItem>Create a Copy</MenuItem>
					</MenuList>
				</React.Fragment>
			)}
		</Menu>
	);
}
```

##### Menu `id`

`id?: string`

A unique identifier. If provided, children elements will derive their DOM id attributes from this value, so it's important that it is unique in the context of its page.

If the `as` prop is used to render a DOM element, the `id` will also be forwarded along to that element.

### MenuButton

Wraps a DOM `button` that toggles the opening and closing of the dropdown menu. Must be rendered inside of a `<Menu>`.

```jsx
<Menu>
	<MenuButton>Profile</MenuButton>
	{/* ... */}
</Menu>
```

#### MenuButton CSS Selectors

Please see the [styling guide](/styling).

A `<MenuButton>` wraps a normal `<button>` and no styles are applied to it, so any global button styles you have will be applied.

```css
button {
	/* your normal button styles will be applied */
}
```

You can use the `[data-reach-menu-button]` selector to style only the dropdown buttons:

```css
[data-reach-menu-button] {
	color: blue;
}
```

If you'd like to target when the menu is open use `aria-expanded`:

```css
[data-reach-menu-button][aria-expanded="true"] {
	background: #000;
	color: white;
}
```

#### MenuButton Props

| Prop                                       | Type                   | Required |
| ------------------------------------------ | ---------------------- | -------- |
| [`button` props](#menubutton-button-props) |                        |          |
| [`children`](#menubutton-children)         | `node`                 | false    |
| `onMouseDown`                              | `preventableEventFunc` | false    |
| `onKeyDown`                                | `preventableEventFunc` | false    |

##### MenuButton `button` props

Any props not listed above will be spread onto the underlying button element. You can treat it like any other button in your app for styling.

```jsx
<Menu>
	<MenuButton
		className="button-primary"
		style={{ boxShadow: "2px 2px 2px hsla(0, 0%, 0%, 0.25)" }}
	>
		Actions <span aria-hidden>▾</span>
	</MenuButton>
	<MenuList>
		<MenuItem onSelect={() => {}}>Do nothing</MenuItem>
	</MenuList>
</Menu>
```

##### MenuButton `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `MenuButton` what underlying element to render. Defaults to `button`.

<AsPropWarning />

##### MenuButton `children`

`children: React.ReactNode`

Accepts any renderable content.

```jsx
<MenuButton>
	Actions{" "}
	<span aria-hidden>
		<Gear />
	</span>
</MenuButton>
```

### MenuList

Wraps a DOM element that renders the menu items. Must be rendered inside of a `<Menu>`.

```jsx
<Menu>
	{/* ... */}
	<MenuList>
		<MenuItem onSelect={() => {}}>Download</MenuItem>
	</MenuList>
</Menu>
```

#### MenuList CSS Selectors

```css
[data-reach-menu-list] {
	padding: 20px 10px;
}
```

#### MenuList Props

| Prop                                | Type      | Required |
| ----------------------------------- | --------- | -------- |
| [`div` props](#menuitems-div-props) |           |          |
| [`children`](#menuitems-children)   | `node`    | false    |
| [`portal`](#menuitems-portal)       | `boolean` | false    |

##### MenuList `div` props

All props are spread to the underlying element. Here we apply a `className` the element.

```jsx
// jsx-demo
function Example() {
	return (
		<Menu>
			<MenuButton>
				Actions <span aria-hidden>▾</span>
			</MenuButton>
			<MenuList className="slide-down">
				<MenuItem onSelect={() => {}}>Start Video</MenuItem>
				<MenuItem onSelect={() => {}}>Start Screenshare</MenuItem>
				<MenuItem onSelect={() => {}}>Send a Message</MenuItem>
			</MenuList>
		</Menu>
	);
}
```

The stylesheet contains these rules to create the animation.

```css
@keyframes slide-down {
	0% {
		opacity: 0;
		transform: translateY(-10px);
	}
	100% {
		opacity: 1;
		transform: translateY(0);
	}
}

.slide-down[data-reach-menu-list],
.slide-down[data-reach-menu-items] {
	border-radius: 5px;
	animation: slide-down 0.2s ease;
}
```

##### MenuList `children`

`children: React.ReactNode`

Can contain only `MenuItem` or a `MenuLink`

```jsx
<MenuList>
	<MenuItem />
	<MenuLink />
</MenuList>
```

##### MenuList `portal`

`portal?: boolean`

Whether or not the popover should be rendered inside a portal. Defaults to `true`.

### MenuPopover

A low-level wrapper for the popover that appears when a menu button is open. You can compose it with `MenuItems` for more control over the nested components and their rendered DOM nodes, or if you need to nest arbitrary components between the outer wrapper and your list.

```jsx
<Menu>
	{/* ... */}
	<MenuPopover>
		<div className="arbitrary-element">
			<MenuItems>
				<MenuItem onSelect={() => {}}>Download</MenuItem>
			</MenuItems>
		</div>
	</MenuPopover>
</Menu>
```

#### MenuPopover CSS Selectors

```css
[data-reach-menu-popover] {
}
```

#### MenuPopover Props

| Prop                                | Type      | Required |
| ----------------------------------- | --------- | -------- |
| [`children`](#menupopover-children) | `node`    | true     |
| [`portal`](#menupopover-portal)     | `boolean` | false    |
| [`position`](#menupopover-position) | `func`    | false    |

##### MenuPopover `children` props

`children: React.ReactNode`

##### MenuPopover `portal`

`portal?: boolean`

Whether or not the popover should be rendered inside a portal. Defaults to `true`.

##### MenuPopover `position`

`position?: (targetRect?: DOMRect | null, popoverRect?: DOMRect | null) => React.CSSProperties`

A function used to determine the position of the popover in relation to the menu button. By default, the menu button will attempt to position the popover below the button aligned with its left edge. If this positioning results in collisions with any side of the window, the popover will be anchored to a different side to avoid those collisions if possible.

The function accepts the DOMRect of the menu button element, and the DOMRect of the popover element as arguments, and returns a CSS properties object that is then applied to the popover as inline styles.

### MenuItems

A low-level wrapper for menu items. Compose it with `MenuPopover` for more control over the nested components and their rendered DOM nodes, or if you need to nest arbitrary components between the outer wrapper and your list.

See [`MenuPopover`](#menupopover) for details.

#### MenuItems CSS Selectors

```css
[data-reach-menu-items] {
}
```

### MenuItem

Handles menu selection. Must be a direct child of a `<MenuList>`.

```jsx
<MenuList>
	<MenuItem onSelect={() => alert("download!")}>Download</MenuItem>
</MenuList>
```

#### MenuItem CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-menu-item] {
	padding: 20px 10px;
}
```

To change the styles of a highlighted menu item, use this pseudo-pseudo selector:

```css
[data-reach-menu-item][data-selected] {
	background: red;
}
```

The following example has this css applied:

```css
.red-highlight[data-reach-menu-item][data-selected] {
	background: red;
}
```

```jsx
// jsx-demo
function Example() {
	return (
		<Menu>
			<MenuButton>
				Actions <span aria-hidden>▾</span>
			</MenuButton>
			<MenuList>
				<MenuItem className="red-highlight" onSelect={() => {}}>
					Start Video
				</MenuItem>
				<MenuItem className="red-highlight" onSelect={() => {}}>
					Start Screenshare
				</MenuItem>
			</MenuList>
		</Menu>
	);
}
```

#### MenuItem Props

| Prop                                     | Type                          | Required |
| ---------------------------------------- | ----------------------------- | -------- |
| [element props](#menuitem-element-props) |                               |          |
| [`as`](#menuitem-as)                     | `string` <Pipe /> `Component` | false    |
| [`children`](#menuitem-children)         | `node`                        | false    |
| [`disabled`](#menuitem-disabled)         | `boolean`                     | false    |
| [`onSelect`](#menuitem-onselect)         | `func`                        | true     |

##### MenuItem element props

All props are spread to the underlying element.

In this example the `onFocus` prop is passed down to the element.

```jsx
// jsx-demo
function Example(props) {
	const [focusCount, setFocusCount] = React.useState(0);
	return (
		<Menu>
			<MenuButton>Actions</MenuButton>
			<MenuList>
				<MenuItem
					onFocus={() => {
						setFocusCount((prevFocusCount) => prevFocusCount + 1);
					}}
					onSelect={() => {}}
				>
					Focused {focusCount} Times
				</MenuItem>
				<MenuItem onSelect={() => {}}>Start Screenshare</MenuItem>
				<MenuItem onSelect={() => {}}>Send a Message</MenuItem>
			</MenuList>
		</Menu>
	);
}
```

##### MenuItem `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `MenuItem` what underlying element to render. Defaults to `div`.

You can pass another element or use this prop to render a styled component.

```jsx
const StyledItem = styled.div`
	&[data-selected] {
		background: palevioletred;
	}
`;

function Example() {
	return (
		<Menu>
			<MenuButton id="example-button">
				Actions <span aria-hidden="true">▾</span>
			</MenuButton>
			<MenuList>
				<MenuItem as={StyledItem} onSelect={action("Download")}>
					Download
				</MenuItem>
				<MenuItem as={StyledItem} onSelect={action("Copy")}>
					Create a Copy
				</MenuItem>
				<MenuItem as={StyledItem} onSelect={action("Mark as Draft")}>
					Mark as Draft
				</MenuItem>
				<MenuItem as={StyledItem} onSelect={action("Delete")}>
					Delete
				</MenuItem>
			</MenuList>
		</Menu>
	);
}
```

##### MenuItem `children`

`children: React.ReactNode`

You can put any type of content inside of a `<MenuItem>`.

```jsx
// jsx-demo
function Example(props) {
	return (
		<Menu>
			<MenuButton>
				Your Cats <span aria-hidden>▾</span>
			</MenuButton>
			<MenuList className="kittys">
				<MenuItem onSelect={() => {}}>
					<img
						src="https://placekitten.com/100/100"
						alt="Fluffybuns the destroyer"
					/>
					<span>Fluffybuns the Destroyer</span>
				</MenuItem>
				<MenuItem onSelect={() => {}}>
					<img src="https://placekitten.com/120/120" alt="Simon the pensive" />
					<span>Simon the pensive</span>
				</MenuItem>
			</MenuList>
		</Menu>
	);
}
```

##### MenuItem `disabled`

`disabled?: boolean`

Whether or not the item is disabled from selection and navigation.

##### MenuItem `onSelect`

`onSelect(): void`

Callback that fires when a `MenuItem` is selected.

### MenuLink

Handles linking to a different page in the menu. By default it renders `<a>`, but also accepts any other kind of Link as long as the `Link` uses the `React.forwardRef` API.

Must be a direct child of a `<MenuList>`.

```jsx
import { Link } from "@reach/router";
<MenuList>
	<MenuLink as={Link} to="somewhere/else">
		Somewhere w/ Reach Router
	</MenuLink>
	<MenuLink href="https://reactjs.org">Official React Site</MenuLink>
	<MenuLink as={GatsbyLink} to="/somewhere/with/gatsby">
		Some Gatsby Page
	</MenuLink>
</MenuList>;
```

#### MenuLink CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-menu-item] {
	padding: 20px 10px;
}
```

To change the styles of a highlighted menu item, use this pseudo-pseudo selector:

```css
[data-reach-menu-item][data-selected] {
	background: red;
}
```

#### MenuLink Props

| Prop                                     | Type                          | Required |
| ---------------------------------------- | ----------------------------- | -------- |
| [element props](#menulink-element-props) |                               |          |
| [`as`](#menulink-as)                     | `string` <Pipe /> `Component` | false    |
| [`children`](#menulink-children)         | `node`                        | false    |
| [`disabled`](#menulink-disabled)         | `boolean`                     | false    |
| [`onSelect`](#menulink-onselect)         | `func`                        | false    |

##### MenuLink element props

All props are spread to the underlying element

```jsx
// the `to` prop is spread onto the Reach Router Link
<MenuLink as={Link} to="somewhere/else">
  Somewhere
</MenuLink>

// the `href` prop is spread onto the underlying `a`
<MenuLink href="https://reactjs.org">Official React Site</MenuLink>
```

##### MenuLink `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `MenuLink` what underlying element to render. Defaults to `a`.

While `MenuLink` should always render an HTML anchor tag, this is useful to pass a styled component or if you are using a router and need to use its `Link` component.

```jsx
import { Link } from "react-router";
<Menu>
	<MenuButton>Products</MenuButton>
	<MenuList>
		<MenuLink as={Link} to="/settings">
			Settings
		</MenuLink>
		<MenuLink href="https://reacttraining.com/workshops">Workshops</MenuLink>
		<MenuLink href="https://reacttraining.com/courses">Online Courses</MenuLink>
	</MenuList>
</Menu>;
```

Additionally, if other routers' `Link` component uses the `React.forwardRef` API, you can pass them in as well. If they don’t it won't work because we will not be able to manage focus on the element the component renders.

```jsx
import { Link } from "gatsby";
<MenuLink as={GatsbyLink} to="/somewhere" />;
```

##### MenuLink `children`

`children: React.ReactNode`

You can render any kind of content inside of a MenuLink.

```jsx
<MenuLink>
	<ProfileImage userId="4" />
	<UserName>Ryan Florence</UserName>
</MenuLink>
```

##### MenuLink `disabled`

`disabled?: boolean`

Whether or not the link is disabled from selection and navigation.

##### MenuLink `onSelect`

`onSelect?(): void`

Callback that fires when a `MenuLink` is selected.

### useMenuButtonContext

`function useMenuButtonContext(): { isExpanded: boolean }`

A hook that exposes data for a given `Menu` component to its descendants.

## Notes

### Unmounting the Menu after an action

If one of your menu items causes the `<Menu>` itself to unmount, it is your job to move focus to the changed content. One exception to this is if you're using `MenuLink` and Reach Router. In this case, the router will handle focus for you.

Note the callbacks given to `setState` in the following demo app where focus is managed between screens. If you don't do this you'll drop keyboard and screen reader users off at the top of the document. It'll then be hard for them to know what changed and how to find it. Moving focus helps them stay where you want them the very same way visual design does.

```jsx
function Example(props) {
	const screen1FocusRef = React.useRef();
	const screen2ButtonFocusRef = React.useRef();
	React.useEffect(() => {
		requestAnimationFrame(() => {
			if (screen === 1) {
				screen1FocusRef.current.focus();
			}
			if (screen === 2) {
				screen2ButtonFocusRef.current.focus();
			}
		});
	}, [screen]);

	const [screen, setScreen] = React.useState(1);
	if (screen === 1) {
		return (
			<div ref={screen1FocusRef} tabIndex="-1">
				<h4>Screen One</h4>
				<Menu>
					<MenuButton>Actions</MenuButton>
					<MenuList>
						<MenuItem onSelect={() => setScreen(2)}>Go to screen 2</MenuItem>
						<MenuItem onSelect={() => {}}>Do nothing</MenuItem>
					</MenuList>
				</Menu>
				<Menu />
			</div>
		);
	}
	if (screen === 2) {
		return (
			<div>
				<h4>Screen 2</h4>
				<button ref={screen2ButtonFocusRef} onClick={() => setScreen(1)}>
					Back to screen 1
				</button>
			</div>
		);
	}
	return null;
}
```

### Icons

If you add an icon to indicate to users the button is a dropdown menu, use `aria-hidden` on the icon. Screen readers will already announce to the user that the element is a dropdown menu; adding a label to your icon would be redundant.

```jsx
<MenuButton>
	Actions <span aria-hidden>▾</span>
</MenuButton>
```

However, if you have no text and only an icon, please make sure your icon has a screen reader friendly label:

```jsx
// we'd rather it said "Actions" than
// "downward pointing triangle"
<MenuButton>
  <span aria-label="Actions">▾</span>
</MenuButton>

// add screen reader only text for svgs
import { VisuallyHidden } from "@reach/visually-hidden"
<MenuButton>
  <VisuallyHidden>Actions</VisuallyHidden>
  <svg aria-hidden>
    <polygon points="0,0 20,0 10,10 " />
  </svg>
</MenuButton>

// and your images an alt attribute
<MenuButton>
  <img src="gear.png" alt="gear"/>
</MenuButton>

// Or just label the button and hide everything
<MenuButton aria-label="Actions">
  <span aria-hidden>
    <TripleDots/>
  </span>
</MenuButton>
```

### Events

You may want to pass your own click handler to a MenuButton, MenuItem or MenuLink that, in some cases, intercepts and prevents the default behavior from ocurring. You may think to do this by passing a handler to the `onClick` prop:

```jsx
<Menu>
	<MenuButton>Actions</MenuButton>
	<MenuList>
		<MenuItem
			onClick={(event) => {
				if (!userLoggedIn) {
					event.preventDefault();
					openDialog();
				}
			}}
			onSelect={() => alert("Download")}
		>
			Download
		</MenuItem>
	</MenuList>
</Menu>
```

This won't work because we actually do not call the `onClick` handler to activate `Menu` or select `MenuItem` or `MenuLink` components. This is because a user may mouse down on a menu button, drag over a menu item and then select it by releasing the mouse trigger. A user may also start clicking one item, then drag to another item before `mouseup` to change their selectiion.

As such, events for each component look more like this:

- `MenuButton`: Activates `Menu` in `onMouseDown` or `onKeyDown` (<kbd>Enter</kbd> or <kbd>Spacebar</kbd> keys)
- `MenuItem`: Selects itself in `onMouseUp` or `onKeyDown` (<kbd>Enter</kbd> or <kbd>Spacebar</kbd> keys)
- `MenuLink`: Selects itself in `onMouseUp` or `onKeyDown` (<kbd>Enter</kbd> or <kbd>Spacebar</kbd> keys).
  - For `MenuLink`, the `click` event is fired after the selection events. So if you only need to intercept the event that triggers the anchor link, you can still use `onClick`, but the rest of the event handlers called in `MenuLink` will still be which means the `Menu` will close and your `onSelect` handler will be triggered.

### Keyboard Accessibility

| Key                  | Action                                                                         |
| -------------------- | ------------------------------------------------------------------------------ |
| <kbd>Enter</kbd>     | Open/close while focused on the `MenuButton`; Selects an item when navigating. |
| <kbd>Spacebar</kbd>  | Open/close while focused on the `MenuButton`; Selects an item when navigating. |
| <kbd>ArrowUp</kbd>   | Highlight previous item                                                        |
| <kbd>ArrowDown</kbd> | Highlight next item                                                            |
| <kbd>Home</kbd>      | Highlight first item                                                           |
| <kbd>End</kbd>       | Highlight last item                                                            |
| <kbd>Escape</kbd>    | Close menu                                                                     |
| <kbd>Tab</kbd>       | No effect                                                                      |
| Type characters      | Highlights matching item                                                       |
